/*
 Copyright (C) 2011  Matteo Magnani, Gabriele Nunziante
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/***************************************************************************
 * CRS : Server to retrieve conversations                                  *
 ***************************************************************************
 * 
 ***************************************************************************/

import java.io.*;
import java.net.*;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import org.apache.lucene.analysis.*;
import org.apache.lucene.document.*;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.*;
import org.apache.lucene.search.*;
import org.apache.lucene.queryParser.*;
import org.apache.lucene.util.*;

public class CRSearchServer extends Thread {
	
	/* DEBUG variables */
	boolean q = false; /* true to print queries */
	boolean t = false; /* true to print tweets */
	boolean s = false; /* true to print system messages */
	
	/* Configuration variables [read from file crstoreserver.cfg] */
	private static int port; // Server listening on this port
	private static String indexDir; // directory where the index will be stored	
	private static String dbServerAddress; // Database address
	private static String dbName; // Database name
	private static String dbUser; // Database user
	private static String dbPassword; // Database password
	
	//private URLConnection conn;
	private static ServerSocket serversocket;
	private static IndexSearcher searcher;
	//static String search, server;
	
	/* DB connection */
	private static Database db;
	
	public static void main(String[] args) throws Exception
	{
		/* Reads configuration */
		Properties p = new Properties();
		try {
			p.load(new FileReader("crservers.cfg"));
		}
		catch (IOException ex) {
			//System.err.println("[ERR] " + ex.getMessage());
			System.err.println("Cannot open config file crservers.cfg");
			System.exit(-1);
		}
		try {
			port = Integer.parseInt(p.getProperty("search-port"));
		}
		catch (Exception ex) {
			//System.err.println("[ERR] " + ex.getMessage());
			System.err.println("<search-port> config info not found");
			System.exit(-1);
		}
		try {
			indexDir = p.getProperty("indexDir");
			if (indexDir==null) throw new Exception("Could not read <indexDir> config parameter");
			dbServerAddress = p.getProperty("dbServerAddress");
			if (dbServerAddress==null) throw new Exception("Could not read <dbServerAddress> config parameter");
			dbName = p.getProperty("dbName");
			if (dbName==null) throw new Exception("Could not read <dbName> config parameter");
			dbUser = p.getProperty("dbUser");
			if (dbUser==null) throw new Exception("Could not read <dbUser> config parameter");
			dbPassword = p.getProperty("dbPassword");
			if (dbPassword==null) throw new Exception("Could not read <dbPassword> config parameter");
		}
		catch (Exception ex) {
			System.err.println("[ERR] " + ex.getMessage());
			System.exit(-1);
		}
		
		/* starts the IR index */
		try { 
			searcher = new IndexSearcher(FSDirectory.open(new File(indexDir)));		
			searcher.close();
		}
		catch (Exception ex) {
			System.err.println("[ERR] " + ex.getMessage());
			System.err.println("Cannot setup LUCENE index");
			System.exit(-1);
		}
		
		/* Getting database connection */
		db = new Database(dbServerAddress, dbName, dbUser, dbPassword);
		if(!db.open()) {
			System.out.println("[ERR] DB Connection error: " + db.getError());
			System.exit(-1);
		}
		db.disconnect();
		
		/* Opens the server socket */
		try {
			serversocket = new ServerSocket(port);
		}
		catch (Exception ex) {
			System.err.println("[ERR] " + ex.getMessage());
			System.err.println("Cannot start the server on port " + port);
			System.exit(-1);
		}
		
		System.out.println("\n* CRSearchServer waiting on port " + port);
		
		new CRSearchServer(dbServerAddress, dbName, dbUser, dbPassword);
	}
	
	public CRSearchServer(String dbServerAddress, String dbName, String dbUser, String dbPassword) throws Exception {
		while(true) {
			// Wait for connection and start thread
			try {
				Socket client = serversocket.accept();
				if (s) System.out.println("* Connection from:   " + client.getInetAddress());
				SearchConnect c = new SearchConnect(client, dbServerAddress, dbName, dbUser, dbPassword, indexDir);
			} catch(Exception e) { 
				System.err.println("[CRSearchServer] [ERR]" + e.getMessage());
			}
		}        
	}
}

/*************************************************************************************************/

/* Class to manage a client connection */
class SearchConnect extends Thread 
{	
	/* DEBUG variables */
	boolean q = true; /* true to print queries */
	boolean t = true; /* true to print tweets */
	boolean s = true; /* true to print system messages */	
	
	/*  */
	URLConnection conn;
	private Socket client = null;
	BufferedReader in = null;
	PrintStream out = null;
	IndexSearcher searcher;
	Database db;
	
	public SearchConnect(Socket clientSocket, String dbServerAddress, String dbName, String dbUser, String dbPassword, String indexDir) {		
		this.client = clientSocket;
		
		try {
			/* searcher */
			this.searcher = new IndexSearcher(FSDirectory.open(new File(indexDir)));
			/* input/output */
			in = new BufferedReader(new InputStreamReader(client.getInputStream()));
			out = new PrintStream(client.getOutputStream(), true);
			/* Getting database connection */
			db = new Database(dbServerAddress, dbName, dbUser, dbPassword);
			if (!db.open()) {
				System.out.println("[ERR] DB Connection error: " + db.getError());
				System.exit(-1);
			}
		}
		catch (Exception ex) {
			try { client.close(); }
			catch (Exception e) { System.out.println(e.getMessage());}
			return;
		}
		this.start();
	}
	
	public void run() {
		try {
			//if (s) System.out.println("* Processing connection from " + client.getInetAddress());
			String cmd = in.readLine();
			String[] f = cmd.split("\t");
			String res = search(f[0],Double.parseDouble(f[1]),Double.parseDouble(f[2]),Double.parseDouble(f[3]),
								Double.parseDouble(f[4]),Double.parseDouble(f[5]),Integer.parseInt(f[6]));
			out.println(res);
			out.flush();
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			db.disconnect();
			searcher.close();
			out.close();
			in.close();
			client.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * @param 
	 * @param 
	 * @param 
	 * @param 
	 * @param
	 * @return  
	 */
	public String search(String querystr, double text_search, double retweet_search, double timeliness_search,
						 double density_search, double pop_search, int pageNumber) throws Exception {
		int hitsPerPage = 10000;
		/* Variables for the normalization of scores */
		double max_score = 0;
		double max_time_density = 0.0;
		int max_n_retweet = 0;
		int max_reply = 0;
		int max_n_story = 0;
		int max_n_reply = 0;
		int max_followers = 0;
		Date max_time = new Date(0000,0,0,0,0,0);
		Timestamp max_time_2 = null;
		long max_time_start = 0;
		
		// perform keyword search
		Query q = new QueryParser(Version.LUCENE_31, "text", new SimpleAnalyzer(Version.LUCENE_31)).parse(querystr);
		TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage, true);
		searcher.search(q, collector);
		ScoreDoc[] hits = collector.topDocs().scoreDocs;
		if (s) System.out.println("\nFound " + hits.length + " results for '"+querystr+"' ("+hitsPerPage+" max)");
		
		ArrayList<CRSearchResult> l = new ArrayList<CRSearchResult>();
		
		/* Text Ranking */
		for (int i=0; i<hits.length; ++i) {
			int docId = hits[i].doc;
			Document d = searcher.doc(docId);
			int k = 0;
			CRSearchResult res = new CRSearchResult();
			res.id_start = d.get("id");
						
			res.n_reply = Integer.parseInt(d.get("num_tweets"));
			//System.out.println(d.get("n_retweets"));
			res.n_retweet = 0;//Integer.parseInt(d.get("n_retweets"));
			res.followers = Integer.parseInt(d.get("pop"));
			double dens = Double.parseDouble(d.get("den"));
			if (dens>0) {
				res.time_density = 1.0/dens;
			}
			else res.time_density = 0;
			res.score = hits[i].score;
			
			
			max_score = Math.max(max_score,res.score);
			max_time_density = Math.max(max_time_density,res.time_density);
			max_n_retweet = Math.max(max_n_retweet,res.n_retweet);
			max_n_reply = Math.max(max_n_reply,res.n_reply);
			max_followers = Math.max(max_followers,res.followers);
			
			l.add(res);
		}
		
		for (CRSearchResult res: l) {
			res.setMax(max_score,max_time_density,max_n_retweet,0,max_n_reply,max_followers);
			res.total(text_search,  retweet_search,  timeliness_search,  density_search,  pop_search);
		}
		
		Collections.sort(l);

		
		String res = hits.length + "\n";
		
		if (pageNumber<1 || pageNumber > l.size()/10+1)
			pageNumber = 1;
		
		for (int i=0;i<Math.min(10,l.size()-(pageNumber-1)*10);i++) {
			ResultSet rs2 = db.executeQuery("SELECT * FROM tweet WHERE tweet.id='"+l.get((pageNumber-1)*10+i).id_start+"'");
			
			while( rs2!=null && rs2.next() ) {
				res = res + l.get((pageNumber-1)*10+i).total + "\t" + rs2.getString("id") + "\t" + rs2.getString("time") + "\t" + rs2.getString("user_name") + "\t" + rs2.getString("text");
				if (rs2.getString("origin").equals("-1"))
					res = res + "\tN\n";
				else res = res + "\tY\n"; 
			}
		}
		return res;
	}
}
